package sc.digital.human;

import com.google.common.collect.EvictingQueue;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Iterator;

public class App extends JFrame implements WindowFocusListener, AWTEventListener, ActionListener, Human.Callback {
    private JToggleButton speaking;
    private JPanel drawSpeaking;
    private int speakDuration;
    private final Talk talk = new Talk();
    private final Setup setup = new Setup(this);
    private final Timer timer = new Timer(0, this);
    private final EvictingQueue<Short> queue = EvictingQueue.create(500);
    private final Human human = new Human(Human.IPName.GUANGUAN, Tsn.Per.PER0, Chat.Model.ERNIE);

    public App() {
        super.setTitle("三川数字人（演示）");
        super.setIconImage(Config.LOGO);
        super.setPreferredSize(new Dimension(800, 600));
        super.setDefaultCloseOperation(EXIT_ON_CLOSE);
        super.setLayout(new BorderLayout());
        super.addWindowFocusListener(this);
        super.add(new JSplitPane() {{
            this.setLeftComponent(new JPanel() {{
                this.setLayout(new BorderLayout());
                this.add(human, BorderLayout.CENTER);
                this.add(new JPanel() {{
                    this.setLayout(new FlowLayout());
                    this.add(new JComboBox<Human.IPName>(Human.IPName.values()) {{
                        this.setFocusable(false);
                        this.setToolTipText("形象");
                        this.setSelectedItem(human.getIPName());
                        this.addActionListener((e) -> human.stop((Human.IPName) this.getSelectedItem()));
                    }});
                    this.add(new JComboBox<Tsn.Per>(Tsn.Per.values()) {{
                        this.setFocusable(false);
                        this.setToolTipText("音库");
                        this.setSelectedItem(human.getPer());
                        this.addActionListener((e) -> human.setPer((Tsn.Per) this.getSelectedItem()));
                    }});
                }}, BorderLayout.SOUTH);
            }});
            this.setRightComponent(new JPanel() {{
                this.setLayout(new BorderLayout());
                this.add(talk, BorderLayout.CENTER);
                this.add(new JPanel() {{
                    this.setLayout(new BorderLayout());
                    this.add(new JPanel() {{
                        this.setLayout(new FlowLayout());
                        this.add(new JButton("设置") {{
                            this.setFocusable(false);
                            this.setFocusPainted(false);
                            this.addActionListener((e) -> setup.setVisible(true));
                        }});
                    }}, BorderLayout.WEST);
                    this.add(drawSpeaking = new JPanel() {
                        @Override
                        protected void paintComponent(Graphics g) {
                            super.paintComponent(g);
                            drawSpeaking(g);
                        }
                    }, BorderLayout.CENTER);
                    this.add(new JPanel() {{
                        this.setLayout(new FlowLayout());
                        this.add(new JComboBox<Chat.Model>(Chat.Model.values()) {{
                            this.setFocusable(false);
                            this.setSelectedItem(human.getModel());
                            this.addActionListener((e) -> human.setModel((Chat.Model) this.getSelectedItem()));
                        }});
                        this.add(speaking = new JToggleButton("按下 说话"));
                    }}, BorderLayout.EAST);
                }}, BorderLayout.SOUTH);
            }});
        }}, BorderLayout.CENTER);
        super.pack();
        super.setLocationRelativeTo(null);

        this.speaking.setFocusable(false);
        this.speaking.setFocusPainted(false);
        this.speaking.setToolTipText("按下空格或回车键说话");
        this.speaking.addActionListener((e) -> {
            if (this.speaking.isSelected()) {
                this.speakDuration = Config.SPEAK_DURATION * 1000;
                this.human.listen((buffer, offset, length) -> this.drawSpeaking(buffer));
                this.timer.setDelay(1000);
                this.timer.start();
            } else {
                this.timer.stop();
                this.human.say(this);
                this.speaking.setText("按下 说话");
            }
        });
    }

    @Override
    public void windowGainedFocus(WindowEvent e) {
        Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
    }

    @Override
    public void windowLostFocus(WindowEvent e) {
        Toolkit.getDefaultToolkit().removeAWTEventListener(this);
    }

    @Override
    public void eventDispatched(AWTEvent event) {
        if (event instanceof KeyEvent) {
            KeyEvent e = (KeyEvent) event;
            if (event.getID() == KeyEvent.KEY_PRESSED
                    && (e.getKeyCode() == KeyEvent.VK_SPACE || e.getKeyCode() == KeyEvent.VK_ENTER)) {
                this.speaking.doClick();
            }
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == this.timer) {
            if (this.speakDuration > this.timer.getInitialDelay()) {
                this.speaking.setText("按下 说话（" + (this.speakDuration / this.timer.getDelay()) + "s）");
            } else {
                this.speaking.doClick();
            }
            this.speakDuration = speakDuration - this.timer.getDelay();
        }
    }

    @Override
    public void questions(String questions) {
        this.talk.talk(questions, null, 0);
    }

    @Override
    public void answers(String answers, float audioSeconds) {
        this.talk.talk(answers, this.human.getIPName(), audioSeconds);
    }

    public void say(String say) {
        this.human.say(this, say);
    }

    void drawSpeaking(Graphics g) {
        g.setColor(Color.BLACK);
        g.translate(0, this.drawSpeaking.getHeight() / 2);
        synchronized (this.queue) {
            if (this.queue.size() > 1) {
                Iterator<Short> it = this.queue.iterator();
                Short p1 = it.next();
                Short p2 = it.next();
                int x1 = 0, x2 = 0;
                while (it.hasNext()) {
                    g.drawLine(x1, (int) (p1), x2, (int) (p2));
                    p1 = p2;
                    p2 = it.next();
                    x1 = x2;
                    x2 += 1;
                }
            }
        }
    }

    void drawSpeaking(byte[] buffer) {
        for (byte b : buffer) {
            synchronized (this.queue) {
                this.queue.add((short) b);
            }
        }
        SwingUtilities.invokeLater(() -> this.drawSpeaking.repaint());
    }


    public static void main(String[] args) throws Exception {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
            StringWriter stringWriter = new StringWriter();
            e.printStackTrace(new PrintWriter(stringWriter));
            JOptionPane.showMessageDialog(null
                    , new JScrollPane(new JTextArea(stringWriter.toString(), 10, 0))
                    , t.toString(), JOptionPane.ERROR_MESSAGE);
        });
        Config.readConfig();
        Config.readCmdlineArgs(args);
        SwingUtilities.invokeLater(() -> {
            App app = new App();
            app.setVisible(true);
            app.say("你好，欢迎使用三川数字人！");
        });
    }
}